#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

// 创建随机数种子，让rand()出来的伪随机不要那么伪，更随机一点
#define MakeSeed() srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234) 

#define PROCSS_NUM 10 // 本次进程池的进程数量

///////////////////////////////////////子进程要完成的某种任务 -- 模拟一下/////////////////////////
// 函数指针 类型
typedef void (*func_t)();

void downLoadTask()
{
    std::cout << getpid() << ": 下载任务\n"
              << std::endl;
    sleep(1);
}

void ioTask()
{
    std::cout << getpid() << ": IO任务\n"
              << std::endl;
    sleep(1);
}

void flushTask()
{
    std::cout << getpid() << ": 刷新任务\n"
              << std::endl;
    sleep(1);
}

// 将任务装进vector中，这样就可以通过下标来给子进程随机派发任务
void loadTaskFunc(std::vector<func_t> *out)
{
    assert(out);
    out->push_back(downLoadTask);
    out->push_back(ioTask);
    out->push_back(flushTask);
}

/////////////////////////////////////////////////下面的代码是一个多进程程序//////////////////////
//这是一个类，专门用于维护父进程与子进程通信的信道【也就是维护匿名管道】
class subEp // Endpoint
{
public:
    // 构造函数
    subEp(pid_t subId, int writeFd)
        : subId_(subId), writeFd_(writeFd)
    {
        char nameBuffer[1024];
        snprintf(nameBuffer, sizeof nameBuffer, "process-%d[pid(%d)-fd(%d)]", num++, subId_, writeFd_);
        name_ = nameBuffer;
    }

public:
    static int num; // 用于在输出时得到此时是第几个子进程
    std::string name_; //用于记载，该子进程是第几个，pid是多少，对应的管道的写端文件描述符
    pid_t subId_; // 该子进程pid
    int writeFd_; // 该子进程的管道的写端文件描述符
};

int subEp::num = 1; 

// 用于子进程接受父进程发来的数据
int recvTask(int readFd)
{
    // 这里用code装父进程发给子进程的任务码
    int code = 0; 
    ssize_t s = read(readFd, &code, sizeof code); //【如果没有任务码就会阻塞，如果写端关闭，read返回0】
    if(s == 4) // 判断是否读到4字节
        return code; // 读取到了任务码，就返回
    else if(s <= 0) 
        return -1; // 这里不能返回0，因为随机到的任务下标可能就是0
    else 
        return 0; //这个情况是不会发生的
}

// 父进程给子进程发送任务码【通信】
void sendTask(const subEp &process, int taskNum)
{
    std::cout << "send task num: " << taskNum << " send to -> " << process.name_ << std::endl; //类中记载的name_就派上用场了
    int n = write(process.writeFd_, &taskNum, sizeof(taskNum)); // 通信-->发送任务码【就是一个下标】
    assert(n == sizeof(int)); // 如果写入的数据不是4个字节，就是有问题的
    (void)n; // n用不到了，告知编译器
}

// 创建子进程，并让子进程执行任务
void createSubProcess(std::vector<subEp> *subs, std::vector<func_t> &funcMap)
{
    std::vector<int> deleteFd; // 用于记录因子进程拷贝父进程而得到的其他子进程的管道的写端的文件描述符【想不清楚就画图！】
    for (int i = 0; i < PROCSS_NUM; i++)
    {
        int fds[2];
        int n = pipe(fds); // 创建匿名管道
        assert(n == 0);
        (void)n;
        // 父进程打开的文件，是会被子进程共享的
        // 试着多想几轮【随着子进程的创建的数量增多，本应该被删除的文件描述符写端，会存在于下一个子进程的文件描述符表】
        pid_t id = fork();
        if (id == 0)
        {
            // 每次创建出子进程，就是先删除从父进程那里拷贝来的之前子进程的管道的写端文件描述符
            //【这个本就应该由父进程掌控，子进程不该掌控】
            for(int i = 0; i < deleteFd.size(); i++) 
                close(deleteFd[i]); 
            // 子进程, 进行处理任务
            close(fds[1]); // 关闭子进程的自己的管道文件的写端
            // 让子进程不断获取管道数据
            while (true)
            {
                // 1. 获取命令码，如果父进程没有发送，我们子进程应该阻塞
                int commandCode = recvTask(fds[0]); //commandCode就是
                // 2. 完成任务
                if (commandCode >= 0 && commandCode < funcMap.size())
                    funcMap[commandCode](); // 执行
                else if(commandCode == -1) // -1说明父进程写端关闭，管道关闭了
                    break;
            }
            exit(0);
        }
        close(fds[0]); // 关闭父进程的对应的管道的读端
        // 构造的时候把pid，和写端文件描述符记载下来。【其实不记载下来也不会影响运行的，但是为了效果更好】
        subEp sub(id, fds[1]); 
        subs->push_back(sub); // subs这个vector是为了实验效果更好的展示出来，并不是必须得
        deleteFd.push_back(fds[1]); // 将本次子进程对应管道的写端记录下来
    }
}

// 负载均衡，不要让少数进程承担多数任务，负载压力太大了
void loadBlanceContrl(const std::vector<subEp> &subs, const std::vector<func_t> &funcMap, int count)
{
    int processnum = subs.size();
    int tasknum = funcMap.size();
    bool forever = (count == 0 ? true : false);

    while (true)
    {
        // 1. 选择一个子进程 --> std::vector<subEp> -> index - 随机数
        int subIdx = rand() % processnum;
        // 2. 选择一个任务 --> std::vector<func_t> -> index
        int taskIdx = rand() % tasknum;
        // 3. 任务发送给选择的进程
        sendTask(subs[subIdx], taskIdx);
        sleep(1);
        if(!forever)
        {
            count--;
            if(count == 0) 
                break;   
        }
    }
    // write quit -> read 0
    for(int i = 0; i < processnum; i++) 
        close(subs[i].writeFd_); //这里其实关到最后一个子进程的，才会逐次关闭到第一个进程
}

// 进程等待，将每个子进程的资源都释放掉，防止僵尸进程
void waitProcess(std::vector<subEp> processes)
{
    int processnum = processes.size();
    for(int i = 0; i < processnum; i++)
    {
        waitpid(processes[i].subId_, nullptr, 0);
        std::cout << "wait sub process success ...: " << processes[i].subId_ << std::endl;
    }
}

int main()
{
    MakeSeed();
    // 1. 建立子进程并建立和子进程通信的信道[匿名管道]
    // 1.1 加载方法表
    std::vector<func_t> funcMap;
    loadTaskFunc(&funcMap); // 将需要执行的所有任务都加载到这个表中
    // 1.2 创建子进程，并且维护好父子通信信道
    std::vector<subEp> subs;
    createSubProcess(&subs, funcMap);

    // 2. 走到这里就是父进程, 控制子进程，负载均衡的向子进程发送命令码
    int taskCnt = 3; // 0: 永远进行
    loadBlanceContrl(subs, funcMap, taskCnt);

    // 3. 回收子进程信息
    waitProcess(subs);

    return 0;
}